/*******************************************************************************
 * Copyright (c) 2009 Nirav Thaker.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 *******************************************************************************/
package name.nirav.refactoringman.refactorings

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring._
import org.eclipse.core.runtime._
import org.eclipse.jdt.core.dom._
import scala.collection.mutable.HashSet
import org.eclipse.jface.text._
import org.eclipse.text.edits._
import org.eclipse.core.resources._
import org.eclipse.core.runtime.Assert
import org.eclipse.jdt.internal.corext.refactoring.Checks
  
/**
 * @author Nirav Thaker
 */
class StaticCallForwardingRefactoring extends Refactoring {
	var meth : String = _
	var lunit : ICompilationUnit = _
	var newMethodName : String = "DEFAULT"
	var allInvokes : HashSet[MethodInvocation] = new HashSet[MethodInvocation]
    var cu: org.eclipse.jdt.core.dom.CompilationUnit = _

	                                                         
	def visit(unit: CompilationUnit, callback: (MethodInvocation) => Unit){
		val mInvokeVisitor = new ASTVisitor(){
			override def endVisit(invocation: MethodInvocation) = callback(invocation)
		}
		unit accept mInvokeVisitor
 	}
 
	implicit def climber(invokedMethod: MethodInvocation) = new Traverser(invokedMethod)
 
	class Traverser(invokedMethod: MethodInvocation){
	  	def declaringType(): TypeDeclaration={
  			var parent = invokedMethod.getParent
			while(!parent.isInstanceOf[TypeDeclaration]){
				parent = parent getParent
			}
			parent.asInstanceOf[TypeDeclaration]
	  	}

		def declaringMethod(): MethodDeclaration={
		  	var parent = invokedMethod.getParent
			while(!parent.isInstanceOf[MethodDeclaration]){
				parent = parent getParent
			}
			parent.asInstanceOf[MethodDeclaration]
	  	}
	  	def isInStaticScope(): Boolean = {
	  		val mods = invokedMethod.declaringMethod.modifiers.toArray
			var notApplicable = false  
			mods.foreach((x :AnyRef) => if(x.asInstanceOf[Modifier].isStatic) notApplicable = true)
			notApplicable
	  	}
	  	def nameEq(other: String): Boolean ={
	  		invokedMethod.getName.getIdentifier.equals(other)
	  	}  
 	}

	override def checkFinalConditions(monitor : IProgressMonitor) : RefactoringStatus ={
		val status = new RefactoringStatus()
		status
	}
	override def checkInitialConditions(monitor: IProgressMonitor) : RefactoringStatus  = {
		val status = new RefactoringStatus()
		cu = toDOMUnit(lunit)
		visit(cu, x => if(x nameEq meth) allInvokes += x)
		if(allInvokes.exists(_.isInStaticScope))
			status.merge(RefactoringStatus.createWarningStatus("""The selected method has atleast 
					one invocation inside a static method, The refactoring will be applicable
					only to the invocations inside non-static blocks."""))
		status
	}
	override def createChange(monitor: IProgressMonitor) : Change = {
		val validInvokes = allInvokes.filter(!_.isInStaticScope)
		if(validInvokes.isEmpty) return new NullChange
		cu.recordModifications
		val decls = validInvokes.toList.first.declaringType.bodyDeclarations.asInstanceOf[java.util.List[BodyDeclaration]]
		val decl = createMethodDeclaration(newMethodName, validInvokes.toList.first)
		decls.add(decl)
		validInvokes.foreach(_.setName(cu.getAST.newSimpleName(newMethodName)))
		validInvokes.foreach(_.setExpression(null))
		val document= new Document(lunit.getSource)
		val edits = cu.rewrite(document, lunit.getJavaProject.getOptions(true))
		new CompositeChange("Forwarded static calls",Array[Change](createChangeInternal(edits)))
	}
	def createChangeInternal(edits : TextEdit): Change = {
   		val change = new TextFileChange("Text Edit", lunit.getResource.asInstanceOf[IFile])
		change.setTextType("java");
		change.setEdit(edits)
		change
	}
	def createMethodDeclaration(str: String, sampleInvoke: MethodInvocation) : MethodDeclaration = {
		val ast = cu.getAST
		val decl = ast.newMethodDeclaration
		decl.setName(ast.newSimpleName(str))
		val v = decl.modifiers.asInstanceOf[java.util.List[Modifier]]
		v.add(ast.newModifier(Modifier.ModifierKeyword.PROTECTED_KEYWORD))
		decl.setReturnType2(ast.newPrimitiveType(PrimitiveType.VOID))
		decl.setBody(ast.newBlock)
		val statements = decl.getBody.statements.asInstanceOf[java.util.List[Statement]]
		val expr = ast.newMethodInvocation
		expr.setExpression(ast.newSimpleName(sampleInvoke.getExpression.toString))
		expr.setName(ast.newSimpleName(sampleInvoke.getName.getIdentifier))
		statements.add(ast.newExpressionStatement(expr))
		decl
	}
	override def getName :  String = "Introduce Local Indirection";
 
	def toDOMUnit(unit: ICompilationUnit): CompilationUnit = {
		import org.eclipse.jdt.core.dom.CompilationUnit
		val parser = ASTParser.newParser(AST.JLS3)
		parser.setResolveBindings(true)
		parser.setProject(unit.getJavaProject)
		parser setSource unit
		parser.createAST(new NullProgressMonitor).asInstanceOf[CompilationUnit]
	}


	def setOriginalMethod(origMeth: String) = meth = origMeth
	//TODO: Complete this check!@
	def checkOverloading : RefactoringStatus = {
			new RefactoringStatus
	}
	def getOriginalMethod : String = meth

	def setCompilationUnit(unit: ICompilationUnit) = lunit = unit

	def setNewMethod(text: String) : RefactoringStatus = {
 		Assert.isNotNull(text);
		newMethodName = text
		var stat = Checks.checkMethodName(newMethodName, null);
		stat.merge(checkOverloading);
		stat
	}

}
